package com.momoc.multi.chat.room.common.config;

public class SnowFlake {

    // 开始时间戳（2020-01-01）
    private long startEpoch;
    // 每一部分占用的位数
    private final long sequenceBits = 12;
    private final long machineIdBits = 5;
    private final long dataCenterIdBits = 5;
    // 每一部分的最大值
    private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
    private final long maxMachineId = -1L ^ (-1L << machineIdBits);
    private final long maxSequence = -1L ^ (-1L << sequenceBits);
    // 每一部分向左的位移
    private final long machineIdShift = sequenceBits;
    private final long dataCenterIdShift = sequenceBits + machineIdBits;
    private final long timestampLeftShift = sequenceBits + machineIdBits + dataCenterIdBits;

    private long dataCenterId;
    private long machineId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowFlake(long dataCenterId, long machineId) {
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException("DataCenterId can't be greater than MAX_DATACENTER_NUM or less than 0.");
        }
        if (machineId > maxMachineId || machineId < 0) {
            throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0.");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
        this.startEpoch = System.currentTimeMillis();
    }

    // 生成下一个ID
    public synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards.");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & maxSequence;
            if(sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;
        return ((timestamp - startEpoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift)
                | (machineId << machineIdShift) | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}
